/*
 * DatagramBuilder Library: serialize/deserialize data to/from datagram
 * Copyright (C) 2008 Rakov Ilya <RakovIlya@gmail.com>
 *
 * This library can be distributed under the terms of the GNU LGPLv2.
 * See the file COPYING.LIB.
 */


#ifndef DATAGRAM_CONTAINERIMPL_H_
#define DATAGRAM_CONTAINERIMPL_H_


/**
 * @file ContainerImpl.h
 *
 * @brief Container implementation file
 *
 * @date Dec 3, 2008
 * @author Rakov Ilya
 */


class Container::ScopedTransaction: public CRTPScopedTransaction< ScopedTransaction >
{

public:

  ScopedTransaction( Container& container )
  : container_( container ),
    size_     ( container.GetSize() )
  {
  }


  inline void Rollback()
  {
    container_.container_->resize( size_ );
  }


private:

  Container& container_;
  const size_t    size_;

};


Container::Container( bool isInitialized )
  : container_( isInitialized ? new container_t : 0 )
{
}


bool Container::IsInitialized() const
{
  return container_;
}


size_t Container::GetSize() const
{
  BOOST_ASSERT( IsInitialized() );

  return container_->size();
}


void Container::CheckRange( citerator_t bgn, citerator_t end ) const
{
  BOOST_ASSERT( IsInitialized() );

  if (    (    ( bgn <  container_->end() )
           || (( bgn == container_->end() ) && ( bgn == end )) // access to nothing after the end
          )
       && ( bgn >= container_->begin() )
       && ( end <= container_->end()   )
       && ( end >  container_->begin() ) )
    return;

  throw deserialize_error( "try to deserialize more data than exists" );
}


template< class T >
void Container::SerializeValue( const T value )
{
  BOOST_ASSERT( IsInitialized() );

  const T tmp( ToNet(value) );

  std::copy( (cpointer_t)&tmp,
             (cpointer_t)&tmp + sizeof(T), std::back_inserter(*container_) );
}


template< class T >
void Container::SerializeValue( const T value, size_t offset )
{
  BOOST_ASSERT( IsInitialized() );

  iterator_t bgn( container_->begin() );    std::advance( bgn, offset );
  iterator_t end( bgn );                    std::advance( end, sizeof( T ) );

  CheckRange( bgn, end );

  const T tmp( ToNet(value) );

  std::copy( (cpointer_t)&tmp,
             (cpointer_t)&tmp + sizeof(T), bgn );
}


template < class SIZE_T >
void Container::SerializeValue( cpointer_t bgn, cpointer_t end )
{
  BOOST_ASSERT( IsInitialized() );

  const difference_t size = std::distance( bgn, end );

  const signed long long MAX_SIZE = std::numeric_limits<SIZE_T>::max();

  if ( size > MAX_SIZE )
    throw serialize_error( "try to serialize too big variable data" );

  SerializeValue( (SIZE_T)size );

  std::copy( bgn, end, std::back_inserter(*container_) );
}


template< class T >
size_t Container::DeSerializeValue( T &value, size_t offset ) const
{
  BOOST_ASSERT( IsInitialized() );

  citerator_t bgn( container_->begin() );   std::advance( bgn, offset );
  citerator_t end( bgn );                   std::advance( end, sizeof( T ) );

  CheckRange( bgn, end );

  std::copy( bgn, end, (iterator_t::pointer)&value );

  value = ToHost( value );

  return sizeof( T );
}


template< class SIZE_T, class T >
size_t Container::DeSerializeValue( std::back_insert_iterator<T> inserter, size_t offset ) const
{
  BOOST_ASSERT( IsInitialized() );

  SIZE_T dataSize;

  const size_t sizeSize = DeSerializeValue( dataSize, offset );

  citerator_t bgn( container_->begin() ); std::advance( bgn, offset + sizeSize );
  citerator_t end( bgn );                 std::advance( end, dataSize );

  CheckRange( bgn, end );

  std::copy( bgn, end, inserter );

  return sizeSize + dataSize;
}


template< class CharType, class CharTrait >
void Container::ToStream( std::basic_ostream< CharType, CharTrait > &out ) const
{
  BOOST_ASSERT( IsInitialized() );

  copy( container_->begin(), container_->end(), std::ostream_iterator< size_t >( out, "," ) );
}


template< class CharType, class CharTrait >
std::basic_ostream< CharType, CharTrait >&
operator <<( std::basic_ostream<CharType, CharTrait> &out, const Container &obj )
{
  obj.ToStream( out );

  return out;
}


#endif /* DATAGRAM_CONTAINERIMPL_H_ */
